package com.iwdnb.gkgz.application.service.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Lists;
import com.iwdnb.bmnf.common.dingtalk.DingTalkMessage;
import com.iwdnb.bmnf.common.dingtalk.DingTalkService;
import com.iwdnb.bmnf.common.service.CacheService;
import com.iwdnb.bmnf.common.utils.BeanConvertUtils;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.gkgz.application.model.request.AddStrategyTradeStockDataRequest;
import com.iwdnb.gkgz.application.service.StockEmotionService;
import com.iwdnb.gkgz.application.service.TulongdaoService;
import com.iwdnb.gkgz.application.strategy.StockBackValidateService;
import com.iwdnb.gkgz.application.strategy.TulongdaoStockBackValidateService;
import com.iwdnb.gkgz.common.constant.CacheConstants;
import com.iwdnb.gkgz.common.enums.StockSecondIndustryEnums;
import com.iwdnb.gkgz.common.enums.StockSellTypeEnums;
import com.iwdnb.gkgz.common.model.dto.Stock;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StockEmotion;
import com.iwdnb.gkgz.common.model.dto.StockLimitUpData;
import com.iwdnb.gkgz.common.model.dto.StockSupportDTO;
import com.iwdnb.gkgz.common.model.dto.StrategyTradeDTO;
import com.iwdnb.gkgz.common.model.dto.TldMatchInfo;
import com.iwdnb.gkgz.common.model.dto.TldMatchInfo.TldMatchItem;
import com.iwdnb.gkgz.common.model.dto.TldMatchInfo.TldSellInfo;
import com.iwdnb.gkgz.common.quota.FenshiAverage;
import com.iwdnb.gkgz.common.quota.MacdIndicator;
import com.iwdnb.gkgz.common.quota.MovingAverage;
import com.iwdnb.gkgz.common.quota.VolumeRatioIndicator;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.DateUtils;
import com.iwdnb.gkgz.common.utils.RealtimeStockUtils;
import com.iwdnb.gkgz.common.utils.StockBlackListUtils;
import com.iwdnb.gkgz.common.utils.StockFenshiUtils;
import com.iwdnb.gkgz.common.utils.StockRangeUtils;
import com.iwdnb.gkgz.common.utils.StockUtils;
import com.iwdnb.gkgz.common.utils.TulongdaoUtils;
import com.iwdnb.gkgz.common.utils.WorkDayUtils;
import com.iwdnb.gkgz.infrastructure.dao.model.query.StockLimitUpQuery;
import com.iwdnb.gkgz.infrastructure.dao.repo.StockDayDataRepository;
import com.iwdnb.gkgz.infrastructure.dao.repo.StockLimitUpRepository;
import com.iwdnb.gkgz.infrastructure.dao.repo.StockRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import static com.iwdnb.gkgz.common.constant.CacheConstants.TULONGDAO_EARLY_WARNING_CACHE_PREFIX;
import static com.iwdnb.gkgz.common.utils.TulongdaoUtils.ALL_PERIODS;

@Service
@Slf4j
public class TulongdaoServiceImpl implements TulongdaoService {
    @Autowired
    private StockLimitUpRepository stockLimitUpRepository;
    @Autowired
    private StockBackValidateService stockBackValidateService;
    @Autowired
    private StockEmotionService stockEmotionService;
    @Autowired
    private CacheService cacheService;
    @Autowired
    private DingTalkService dingTalkService;
    @Autowired
    private StockDayDataRepository stockDayDataRepository;
    @Autowired
    private StockRepository stockRepository;
    @Autowired
    private Executor executor;

    private String DING_TALK_URL = "https://oapi.dingtalk"
        + ".com/robot/send?access_token=a079cbbaa764379fb898c2c16bd9be407966dfba41e19f5b25a7b015240c0901";

    private static final String SELL_TYPE_LIMIT_UP_HOLD = "limitUpHold";

    private static final String SELL_TYPE_END_SELL = "endSell";

    private static final String SELL_TYPE_RISE_SELL = "riseSell";

    private static final String SELL_TYPE_DIVERGE_SELL = "divergeSell";

    private static final String SELL_TYPE_EXPLODED_SELL = "explodedSell";

    private static final String SELL_TYPE_AVERAGE_PRESS_SELL = "averagePressSell";

    public synchronized void checkSignal(Date date, String code, String sellFlag, boolean realtimeFlag,
        boolean sendDingtalkFlag, boolean forceFlag) {
        String dateStr = DateUtil.formatDate(date);
        StockEmotion coldEmotion = stockEmotionService.getColdEmotion(date);
        if (Objects.nonNull(coldEmotion) && realtimeFlag && !forceFlag) {
            String emotionColdTime = coldEmotion.getDatetime();
            log.info("tulongdao current isColdEmotion,return.");
            String cacheKey = CacheConstants.STOCK_EMOTION_COLD_NOTICE_CACHE_PREFIX + dateStr;
            String cacheValue = cacheService.get(cacheKey);
            if (StringUtils.isBlank(cacheValue)) {
                dingTalkService.sendMessage(DING_TALK_URL, "冰点通知:" + emotionColdTime + "-进入冰点,赶紧抛售持有股票,禁止买入股票",
                    Lists.newArrayList());
                cacheService.put(cacheKey, emotionColdTime);
            }
            return;
        }
        Date beforeDateBegin = DateUtil.beginOfDay(WorkDayUtils.getBeforeWorkDay(date, 3));
        Date beforeDateEnd = DateUtil.endOfDay(WorkDayUtils.getBeforeWorkDay(date, 2));
        log.info("{}-tulongdao checkSignal start.", DateUtil.formatDate(date));
        //三天前涨停的数据
        StockLimitUpQuery query = new StockLimitUpQuery();
        query.setDateBegin(DateUtil.formatDateTime(beforeDateBegin));
        query.setDateEnd(DateUtil.formatDateTime(beforeDateEnd));
        query.setTldFlag(BooleanUtils.FALSE);
        if (StringUtils.isNotBlank(code)) {
            query.setCode(code);
        }
        List<StockLimitUpData> limitUpDataList = stockLimitUpRepository.queryLimitUpList(query);
        if (CollectionUtils.isNotEmpty(limitUpDataList)) {
            log.info("tulongdao checkSignal start.");
            checkBuySignal(limitUpDataList, date, realtimeFlag, sendDingtalkFlag, forceFlag);
        }
        ////2天前涨停的数据
        //beforeDateBegin = DateUtil.beginOfDay(WorkDayUtils.getBeforeWorkDay(date, 2));
        //query.setDateBegin(DateUtil.formatDateTime(beforeDateBegin));
        //query.setDateEnd(DateUtil.formatDateTime(DateUtil.endOfDay(beforeDateBegin)));
        //limitUpDataList = stockLimitUpRepository.queryLimitUpList(query);
        //if (CollectionUtils.isNotEmpty(limitUpDataList)) {
        //    log.info("tulongdao checkSignal twoDayData.");
        //    checkBuySignal(limitUpDataList, DateUtil.formatDate(beforeDateBegin), date, realtimeFlag,
        //    sendDingtalkFlag,
        //        forceFlag);
        //}
        log.info("{}-tulongdao checkSignal finished.", DateUtil.formatDate(date));
        if (BooleanUtils.isTrue(sellFlag)) {
            tldSell(WorkDayUtils.getNextStockTradeDay(date), sendDingtalkFlag);
        }
    }

    @Override
    public void tldSell(Date date, boolean sendDingtalkFlag) {
        log.info("{}-tulongdao tldSell start.", DateUtil.formatDate(date));

        Date beforeDateBegin = DateUtil.beginOfDay(WorkDayUtils.getBeforeWorkDay(date, 20));
        Date beforeDateEnd = DateUtil.endOfDay(DateUtil.offsetDay(date, -1));
        StockLimitUpQuery query = new StockLimitUpQuery();
        query.setTldBuyTimeBegin(DateUtil.formatDateTime(beforeDateBegin));
        query.setTldBuyTimeEnd(DateUtil.formatDateTime(beforeDateEnd));
        query.setTldFlag(BooleanUtils.TRUE);
        query.setTldBuyFlag(BooleanUtils.TRUE);
        query.setTldSellFlag(BooleanUtils.FALSE);
        List<StockLimitUpData> limitUpDataList = stockLimitUpRepository.queryLimitUpList(query);
        log.info("tulongdao tldSell limitUpDataList size:" + limitUpDataList.size());
        boolean isToday = DateUtil.formatDate(date).equals(DateUtil.formatDate(new Date()));
        if (CollectionUtils.isNotEmpty(limitUpDataList)) {
            Long time = DateUtil.beginOfDay(date).getTime();
            limitUpDataList = limitUpDataList.stream().filter(t -> t.getTldBuyTime().getTime() < time).collect(
                Collectors.toList());
            if (CollectionUtils.isNotEmpty(limitUpDataList)) {
                StockEmotion coldEmotion = stockEmotionService.getColdEmotion(date);
                if (Objects.nonNull(coldEmotion) && isToday) {
                    log.info("tulongdao current isColdEmotion,sellAll");
                    List<String> codeList = limitUpDataList.stream().map(StockLimitUpData::getCode).collect(
                        Collectors.toList());
                    tldManualSell(StringUtils.join(codeList, ","), DateUtil.parseDateTime(coldEmotion.getDatetime()));
                } else {
                    //具体卖出逻辑
                    tldSellLimitUpDataList(limitUpDataList, date, sendDingtalkFlag);
                }

            }
        }
        log.info("{}-tulongdao tldSell finished.", DateUtil.formatDate(date));

    }

    @Override
    public void tldManualSell(String codes, Date date) {
        log.info("{}-tulongdao tldManualSell start.", DateUtil.formatDate(date));
        Date beforeDateBegin = DateUtil.beginOfDay(WorkDayUtils.getBeforeWorkDay(date, 20));
        Date beforeDateEnd = DateUtil.endOfDay(WorkDayUtils.getBeforeWorkDay(date, 1));
        StockLimitUpQuery query = new StockLimitUpQuery();
        query.setTldBuyTimeBegin(DateUtil.formatDateTime(beforeDateBegin));
        query.setTldBuyTimeEnd(DateUtil.formatDateTime(beforeDateEnd));
        query.setTldFlag(BooleanUtils.TRUE);
        query.setTldBuyFlag(BooleanUtils.TRUE);
        query.setTldSellFlag(BooleanUtils.FALSE);
        List<StockLimitUpData> limitUpDataList = stockLimitUpRepository.queryLimitUpList(query);
        log.info("tulongdao tldManualSell limitUpDataList size:" + limitUpDataList.size());
        if (CollectionUtils.isEmpty(limitUpDataList)) {
            log.info("{}-tulongdao tldManualSell data is null,return.", DateUtil.formatDate(date));
            return;
        }
        if (StringUtils.isNotBlank(codes)) {
            List<String> codeList = Lists.newArrayList(codes.split(","));
            limitUpDataList = limitUpDataList.stream().filter(t -> codeList.contains(t.getCode())).collect(
                Collectors.toList());
        }
        if (CollectionUtils.isEmpty(limitUpDataList)) {
            log.info("{}-tulongdao tldManualSell filter data is null,return.", DateUtil.formatDate(date));
            return;
        }
        boolean isToday = DateUtil.formatDate(date).equals(DateUtil.formatDate(new Date()));
        StockDayData stockDayData = null;
        String dateStr = DateUtil.formatDate(date);
        String datetime = DateUtil.format(date, "yyyy-MM-dd HH:mm") + ":00";
        for (StockLimitUpData limitUpData : limitUpDataList) {
            String code = limitUpData.getCode();
            if (!isToday) {
                stockDayData = stockDayDataRepository.queryDate(code, dateStr);
            } else {
                List<StockDayData> fenshiList = StockFenshiUtils.getFenshiList(code);
                if (CollectionUtils.isEmpty(fenshiList)) {
                    stockDayData = RealtimeStockUtils.getRealtimeData(limitUpData.getCode());
                } else {
                    stockDayData = fenshiList.stream().filter(t -> t.getDatetime().equals(datetime)).findAny().orElse(
                        null);
                    if (Objects.isNull(stockDayData)) {
                        stockDayData = RealtimeStockUtils.getRealtimeData(limitUpData.getCode());
                    }
                }
            }
            //涨停板的话，就不执行卖出逻辑
            if (StockUtils.isLimitUp(stockDayData)) {
                continue;
            }
            limitUpData.setTldSellFlag(BooleanUtils.TRUE);
            limitUpData.setTldSellPrice(stockDayData.getClosePrice());
            limitUpData.setTldSellTime(date);
            limitUpData.setTldProfitRate(
                BigDecimalUtils.subStractAndDividePrecent(stockDayData.getClosePrice(), limitUpData.getTldBuyPrice()));
            limitUpData.setTldSellInfo("手动卖出");
            limitUpData.setTldSellRemark("");
            stockLimitUpRepository.saveStockLimitUpData(limitUpData);
        }
        if (CollectionUtils.isNotEmpty(limitUpDataList)) {
            sendSellDingtalkMessage(date, limitUpDataList, true);
        }
        log.info("{}-tulongdao tldManualSell finished.", DateUtil.formatDate(date));
    }

    @Override
    public void tldSetMarketType(String type) {
        String date = DateUtil.formatDate(new Date());
        cacheService.put(CacheConstants.TULONGDAO_MARKET_BAD_CACHE_PREFIX + date, type);
    }

    @Override
    public List<StockSupportDTO> predictTldSupportPrice(String date, String codes) {
        Date d = WorkDayUtils.getNextWorkDay(DateUtil.parseDate(date));
        String cacheKey = "stockSupport:" + DateUtil.formatDate(d);
        //String content = cacheService.get(cacheKey);
        //if (StringUtils.isNotBlank(content)) {
        //    return JSON.parseArray(content, StockSupportDTO.class);
        //}
        Date beforeDateBegin = DateUtil.beginOfDay(WorkDayUtils.getBeforeWorkDay(d, 3));
        Date beforeDateEnd = DateUtil.endOfDay(WorkDayUtils.getBeforeWorkDay(d, 2));
        StockLimitUpQuery query = new StockLimitUpQuery();
        query.setDateBegin(DateUtil.formatDateTime(beforeDateBegin));
        query.setDateEnd(DateUtil.formatDateTime(beforeDateEnd));
        List<StockLimitUpData> limitUpDataList = stockLimitUpRepository.queryLimitUpList(query);
        if (CollectionUtils.isEmpty(limitUpDataList)) {
            return null;
        }
        //过滤掉部分不需要预测的股票
        limitUpDataList = limitUpDataList.stream().filter(t -> {
            if (StringUtils.equals("d", t.getTldFlag())) {
                return false;
            }
            //在预测日期之前买入的过滤掉
            if (BooleanUtils.isTrue(t.getTldFlag()) && BooleanUtils.isTrue(t.getTldBuyFlag())) {
                if (DateUtils.before(t.getTldBuyTime(), d)) {
                    return false;
                }
            }
            return true;
        }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(limitUpDataList)) {
            return null;
        }
        if (StringUtils.isNotBlank(codes)) {
            List<String> codeList = Lists.newArrayList(codes.split(","));
            limitUpDataList = limitUpDataList.stream().filter(t -> codeList.contains(t.getCode())).collect(
                Collectors.toList());
        }
        if (CollectionUtils.isEmpty(limitUpDataList)) {
            return null;
        }
        List<StockSupportDTO> stockSupportDTOList = new ArrayList<>();
        for (StockLimitUpData limitUpData : limitUpDataList) {
            String code = limitUpData.getCode();
            String signalDate = DateUtil.formatDate(limitUpData.getDate());
            List<StockDayData> stockDayDataList = stockDayDataRepository.queryAllBeforeDate(code, date);
            StockDayData data = StockUtils.getStockDayData(stockDayDataList, signalDate);
            int stockImageValue = StockRangeUtils.cacullateStockImage(stockDayDataList, data);
            if (TulongdaoUtils.isLossImageProfitType(code, signalDate, stockImageValue)) {
                continue;
            }
            if (CollectionUtils.isEmpty(stockDayDataList) || stockDayDataList.size() < 100) {
                continue;
            }
            StockSupportDTO stockSupportDTO = TulongdaoUtils.predictTldSupportPrice(code, stockDayDataList,
                limitUpData);

            if (Objects.isNull(stockSupportDTO)) {
                continue;
            }
            //构建模拟明日的数据
            StockDayData nextDayData = buildTomarrowData(stockSupportDTO, stockDayDataList,
                limitUpData.getClosePrice());
            //校验是否符合屠龙刀买点
            stockDayDataList.add(nextDayData);
            MovingAverage.calculateEMA(stockDayDataList, ALL_PERIODS);
            MacdIndicator.calculateMACD(stockDayDataList);
            VolumeRatioIndicator.calculateVolumeRatio(stockDayDataList);
            //TldCheckInfo tldCheckInfo = TulongdaoUtils.checkTldMacthInfo(code, stockDayDataList,signalDate);
            TldMatchInfo matchInfo = matchTldBuyInfo(code, signalDate, stockDayDataList, BooleanUtils.FALSE);
            if (Objects.isNull(matchInfo)) {
                continue;
            }
            stockSupportDTO.setScore(matchInfo.getScore());
            stockSupportDTOList.add(stockSupportDTO);
        }
        if (CollectionUtils.isNotEmpty(stockSupportDTOList)) {
            cacheService.put(cacheKey, JSON.toJSONString(stockSupportDTOList));
        }
        return stockSupportDTOList;
    }

    @Override
    public List<StockSupportDTO> checkPreditction(List<StockSupportDTO> supportDataList, String date) {
        Date d = WorkDayUtils.getNextWorkDay(DateUtil.parseDate(date));
        //如果是今日,就去同步下实时数据
        if (DateUtil.formatDate(d).equals(DateUtil.formatDate(new Date()))) {
            List<String> codeList = supportDataList.stream().map(StockSupportDTO::getCode).collect(Collectors.toList());
            RealtimeStockUtils.syncRealtimeDatas(codeList);
        }
        List<StockSupportDTO> list = new ArrayList<>();
        for (StockSupportDTO stockSupport : supportDataList) {
            String code = stockSupport.getCode();
            //获取次日的股价数据
            StockDayData data = stockDayDataRepository.queryDate(code, date);
            StockDayData afterData = stockDayDataRepository.queryDate(code, DateUtil.formatDate(d));
            //校验预测信息
            TulongdaoUtils.checkPreditction(stockSupport, data, afterData);
            list.add(stockSupport);
        }
        return list;
    }

    @Override
    public JSONObject staticTldInfo(String begin, String end, String codes, String type) {
        StockLimitUpQuery query = new StockLimitUpQuery();
        query.setDateBegin(begin + " 00:00:00");
        query.setDateEnd(end + " 23:59:59");
        query.setTldFlag(BooleanUtils.TRUE);
        query.setTldBuyFlag(BooleanUtils.TRUE);
        query.setTldSellFlag(BooleanUtils.TRUE);
        List<StockLimitUpData> limitUpDataList = stockLimitUpRepository.queryLimitUpList(query);
        if (CollectionUtils.isEmpty(limitUpDataList)) {
            return null;
        }
        if (StringUtils.isNotBlank(codes)) {
            List<String> codeList = Lists.newArrayList(codes.split(","));
            limitUpDataList = limitUpDataList.stream().filter(t -> codeList.contains(t.getCode())).collect(
                Collectors.toList());
        }
        if (CollectionUtils.isEmpty(limitUpDataList)) {
            return null;
        }
        JSONObject result = new JSONObject();
        if (type.equals("1")) {
            int hasUpCount = 0;
            int noUpCount = 0;
            int hasDownCount = 0;
            int noDownCount = 0;
            BigDecimal hasUpProfit = BigDecimal.ZERO;
            BigDecimal noUpProfit = BigDecimal.ZERO;
            BigDecimal hasDownProfit = BigDecimal.ZERO;
            BigDecimal noDownProfit = BigDecimal.ZERO;
            for (StockLimitUpData limitUpData : limitUpDataList) {
                String code = limitUpData.getCode();
                BigDecimal profitRate = limitUpData.getTldProfitRate();
                String limitUpDate = DateUtil.formatDate(limitUpData.getDate());
                String afterOneDate = DateUtil.formatDate(WorkDayUtils.getNextWorkDay(DateUtil.parseDate(limitUpDate)));
                String buyDate = DateUtil.formatDate(limitUpData.getTldBuyTime());
                List<StockDayData> stockDayDataList = stockDayDataRepository.queryAllBeforeDate(code, buyDate);
                StockDayData limitUpdata = StockUtils.getStockDayData(stockDayDataList, limitUpDate);
                StockDayData afterOneData = StockUtils.getStockDayData(stockDayDataList, afterOneDate);
                if (Objects.isNull(afterOneData)) {
                    continue;
                }
                BigDecimal upPrice = BigDecimalUtils.isGe(afterOneData.getClosePrice(), afterOneData.getOpenPrice())
                    ? afterOneData.getClosePrice() : afterOneData.getOpenPrice();
                BigDecimal upLineRate = BigDecimalUtils.subStractAndDividePrecent(afterOneData.getMaxPrice(), upPrice);
                BigDecimal afterRate = BigDecimalUtils.subStractAndDividePrecent(afterOneData.getClosePrice(),
                    limitUpdata.getClosePrice());
                if (BigDecimalUtils.isGe(upLineRate, BigDecimalUtils.one)) {
                    if (BigDecimalUtils.isGe(afterRate, BigDecimalUtils.zeroPointFive)) {
                        log.info("次日涨停上影线校验: {}-{}有上影线,上涨,收益为:{}", code, buyDate, profitRate);
                        hasUpCount++;
                        hasUpProfit = BigDecimalUtils.add(hasUpProfit, profitRate);
                    } else {
                        log.info("次日涨停上影线校验: {}-{}有上影线,下跌,收益为:{}", code, buyDate, profitRate);
                        hasDownCount++;
                        hasDownProfit = BigDecimalUtils.add(hasDownProfit, profitRate);

                    }

                } else {
                    if (BigDecimalUtils.isGe(afterRate, BigDecimalUtils.zeroPointFive)) {
                        log.info("次日涨停上影线校验: {}-{}无上影线,上涨,收益为:{}", code, buyDate, profitRate);
                        noUpCount++;
                        noUpProfit = BigDecimalUtils.add(noUpProfit, profitRate);
                    } else {
                        log.info("次日涨停上影线校验: {}-{}无上影线,下跌收益为:{}", code, buyDate, profitRate);
                        noDownCount++;
                        noDownProfit = BigDecimalUtils.add(noDownProfit, profitRate);
                    }
                }
            }
            result.put("hasUpCount", hasUpCount);
            result.put("hasDownCount", hasDownCount);
            result.put("hasUpProfit", hasUpProfit);
            result.put("hasDownProfit", hasDownProfit);
            result.put("noUpCount", noUpCount);
            result.put("noDownCount", noDownCount);
            result.put("noUpProfit", noUpProfit);
            result.put("noDownProfit", noDownProfit);
        }
        return result;
    }

    private void checkBuySignal(List<StockLimitUpData> limitUpDataList, Date currentDate,
        boolean realtimeFlag, boolean sendDingtalkFlag, boolean forceFlag) {
        String date = DateUtil.formatDate(currentDate);
        String time = DateUtil.formatTime(currentDate);
        List<String> codeList = limitUpDataList.stream().map(StockLimitUpData::getCode).collect(Collectors.toList());
        codeList = StockBlackListUtils.getCanBuyCodeList(codeList);
        Map<String, StockLimitUpData> limitUpDataMap = new HashMap<>();
        for (StockLimitUpData limitUpData : limitUpDataList) {
            limitUpDataMap.put(limitUpData.getCode(), limitUpData);
        }
        List<TldMatchInfo> matchInfoList = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(codeList.size());
        for (String code : codeList) {
            executor.execute(() -> {
                String limitUpDate = DateUtil.formatDate(limitUpDataMap.get(code).getDate());
                TldMatchInfo matchInfo = checkBuyMatchData(code, limitUpDate, date, realtimeFlag);
                if (Objects.nonNull(matchInfo)) {
                    matchInfoList.add(matchInfo);
                    ////实时信息需要验证当前价格在匹配价格之下
                    //if (realtimeFlag) {
                    //    StockDayData data = RealtimeStockUtils.getRealtimeData(code);
                    //    if (Objects.nonNull(data)
                    //        && BigDecimalUtils.isLe(data.getClosePrice(), matchInfo.getMatchItem().getBuyPrice())) {
                    //        matchInfoList.add(matchInfo);
                    //    }
                    //} else {
                    //    matchInfoList.add(matchInfo);
                    //}
                }
                latch.countDown();
            });
        }
        try {
            latch.await(60, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (CollectionUtils.isEmpty(matchInfoList)) {
            log.info("tulongdao no match stock,return.");
            return;
        }
        log.info("tulongdao match stock:{}", matchInfoList.size());

        List<TldMatchInfo> realtimeMatchInfoList = matchInfoList.stream().filter(
            t -> BooleanUtils.isFalse(t.getEarlyWarnFlag())).collect(Collectors.toList());
        List<String> matchCodeList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(realtimeMatchInfoList)) {
            matchCodeList = realtimeMatchInfoList.stream().map(TldMatchInfo::getCode).collect(Collectors.toList());
            for (TldMatchInfo tldMatchInfo : realtimeMatchInfoList) {
                if (!tldMatchInfo.isMatch()) {
                    continue;
                }
                String code = tldMatchInfo.getCode();
                StockLimitUpData data = limitUpDataList.stream().filter(t -> t.getCode().equals(code)).findAny().orElse(
                    null);
                tldMatchInfo.setData(null);
                data.setTldMatchInfo(tldMatchInfo);
                data.setTldFlag(BooleanUtils.TRUE);
                data.setTldBuyFlag(BooleanUtils.TRUE);
                data.setTldBuyTime(DateUtil.parseDateTime(tldMatchInfo.getMatchDate() + " " + time));
                data.setTldBuyPrice(TulongdaoUtils.getMatchItem(tldMatchInfo).getBuyPrice());
                //设置止损价格4个点
                BigDecimal stopLossPrice = BigDecimalUtils.multiply(data.getTldBuyPrice(),
                    BigDecimalUtils.zeroPointNineSeven);
                data.setTldStopLossPrice(stopLossPrice);
                data.setTldScore(tldMatchInfo.getScore());
                stockLimitUpRepository.saveStockLimitUpData(data);
            }
            if (sendDingtalkFlag) {
                sendBuyDingtalkMessage(currentDate, realtimeMatchInfoList, forceFlag);
            }
        }
        List<String> finalMatchCodeList = matchCodeList;
        List<TldMatchInfo> earlyWarningMatchInfoList = matchInfoList.stream().filter(
            t -> BooleanUtils.isTrue(t.getEarlyWarnFlag()) && !finalMatchCodeList.contains(t.getCode())).collect(
            Collectors.toList());
        if (CollectionUtils.isNotEmpty(earlyWarningMatchInfoList)) {
            if (sendDingtalkFlag) {
                sendEarlyWarningBuyDingtalkMessage(currentDate, matchInfoList, forceFlag);
            }
        }
    }

    private void sendBuyDingtalkMessage(Date currentDate, List<TldMatchInfo> matchInfoList,
        boolean forceFlag) {
        if (CollectionUtils.isEmpty(matchInfoList)) {
            return;
        }
        //把买入信号缓存到预警信号中，后期预警信号不做处理
        String cacheKey = TULONGDAO_EARLY_WARNING_CACHE_PREFIX + DateUtil.formatDate(currentDate);
        String content = cacheService.get(cacheKey);
        String newCodes = matchInfoList.stream().map(t -> t.getCode()).collect(Collectors.joining(","));
        content = StringUtils.isBlank(content) ? newCodes : content + "," + newCodes;
        //缓存10小时
        cacheService.put(cacheKey, content, 36000);
        String matchInfo = buildMatchInfoMessage(matchInfoList);
        String message = DateUtil.formatDate(currentDate) + "-股票通知买入:<br/>" + matchInfo;
        if (forceFlag) {
            message += "<br/>**程序测试买入，不要跟进买入**";
        }
        DingTalkMessage.Markdown markdown = new DingTalkMessage.Markdown();
        markdown.setText(message);
        markdown.setTitle("屠龙刀信号通知");
        DingTalkMessage.At at = new DingTalkMessage.At();
        at.setIsAtAll(false);
        at.setAtMobiles(Lists.newArrayList("18057138912"));
        dingTalkService.sendMessage(DING_TALK_URL, new DingTalkMessage(markdown, at));
    }

    private String buildMatchInfoMessage(List<TldMatchInfo> matchInfoList) {
        List<String> messages = new ArrayList<>();
        for (TldMatchInfo matchInfo : matchInfoList) {
            String code = matchInfo.getCode();
            TldMatchItem item = TulongdaoUtils.getMatchItem(matchInfo);
            String dateTime = DateUtil.format(DateUtil.parseDateTime(matchInfo.getMatchDate()), "HH:mm");
            String message = "**" + StockUtils.getSimpleCode(code) + "-" + StockUtils.getStock(code).getName() + "**-"
                + dateTime + ",价格:" + item.getBuyPrice()
                + ",最高价:" + BigDecimalUtils.multiply(item.getBuyPrice(), new BigDecimal(1.005));
            messages.add(message);
        }
        return StringUtils.join(messages, "<br/>");
    }

    private void sendEarlyWarningBuyDingtalkMessage(Date currentDate,
        List<TldMatchInfo> matchInfoList, boolean forceFlag) {
        if (CollectionUtils.isEmpty(matchInfoList)) {
            return;
        }
        String cacheKey = TULONGDAO_EARLY_WARNING_CACHE_PREFIX + DateUtil.formatDate(currentDate);
        String content = cacheService.get(cacheKey);
        if (StringUtils.isNotBlank(content)) {
            List<TldMatchInfo> filterList = new ArrayList<>();
            for (TldMatchInfo tldMatchInfo : matchInfoList) {
                String code = tldMatchInfo.getCode();
                if (!content.contains(code)) {
                    filterList.add(tldMatchInfo);
                }
            }
            if (CollectionUtils.isEmpty(filterList)) {
                return;
            }
            matchInfoList = filterList;
        }
        String matchInfo = buildEarlyWarningMatchInfoMessage(matchInfoList);
        String message = DateUtil.formatDate(currentDate) + "-通知股票预警买入:<br/>" + matchInfo;
        message += "<br/>**请择机进行买入**";
        if (forceFlag) {
            message += "<br/>**程序测试买入，不要跟进买入**";
        }
        DingTalkMessage.Markdown markdown = new DingTalkMessage.Markdown();
        markdown.setText(message);
        markdown.setTitle("屠龙刀预警信号通知");
        DingTalkMessage.At at = new DingTalkMessage.At();
        at.setIsAtAll(false);
        at.setAtMobiles(Lists.newArrayList("18057138912"));
        dingTalkService.sendMessage(DING_TALK_URL, new DingTalkMessage(markdown, at));
        String newCodes = matchInfoList.stream().map(t -> t.getCode()).collect(Collectors.joining(","));
        content = StringUtils.isBlank(content) ? newCodes : content + "," + newCodes;
        //缓存10小时
        cacheService.put(cacheKey, content, 36000);
    }

    private String buildEarlyWarningMatchInfoMessage(List<TldMatchInfo> matchInfoList) {
        List<String> messages = new ArrayList<>();
        BigDecimal maxRate = new BigDecimal(1.005);
        for (TldMatchInfo matchInfo : matchInfoList) {
            String code = matchInfo.getCode();
            TldMatchItem item = TulongdaoUtils.getMatchItem(matchInfo);
            BigDecimal maxPrice = BigDecimalUtils.multiply(item.getBuyPrice(), maxRate);
            String dateTime = DateUtil.format(DateUtil.parseDateTime(matchInfo.getMatchDate()), "HH:mm");
            String message = "**" + StockUtils.getSimpleCode(code) + "-" + StockUtils.getStock(code).getName() + "**-"
                + dateTime + ",支撑:" + item.getAvg() + ",买入价:" + item.getBuyPrice() + ",最高买入价:" + maxPrice;
            messages.add(message);
        }
        return StringUtils.join(messages, "<br/>");
    }

    private TldMatchInfo checkBuyMatchData(String code, String limitUpDate, String date,
        boolean realtimeFlag) {
        Stock stock = StockUtils.getStock(code);
        if (StockSecondIndustryEnums.isBlackIndustry(stock.getSecondIndustry())) {
            log.info("{} is isBlackIndustry,ignore", code);
            return null;
        }
        List<StockDayData> stockDayDataList = stockDayDataRepository.queryAll(code);
        StockDayData data = StockUtils.getStockDayData(stockDayDataList, limitUpDate);
        List<StockDayData> afterDayDataList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, limitUpDate, 3);
        if (CollectionUtils.isEmpty(afterDayDataList)) {
            return null;
        }
        String cacheKey = CacheConstants.TULONGDAO_REALTIME_BLACK_CACHE_PREFIX + date + ":" + code;
        String content = cacheService.get(cacheKey);
        if (StringUtils.isNotBlank(content)) {
            return null;
        }
        //涨停第二天是涨跌停板的忽略
        StockDayData afterOneData = afterDayDataList.get(0);
        if (StockUtils.isLimitUp(afterOneData) || StockUtils.isLimitDown(afterOneData)) {
            log.info("tld checkBuyMatchData {}-{} 为涨/跌停板，不计算屠龙刀。", code, afterOneData.getDate());
            //缓存黑名单10分钟
            cacheService.put(cacheKey, "limitUpDown", 600);
            return null;
        }
        //涨停后第三天涨幅还在15%以上，忽略
        if (realtimeFlag && afterDayDataList.size() == 3) {
            BigDecimal realtimePrice = afterDayDataList.get(2).getClosePrice();
            BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(realtimePrice, data.getClosePrice());
            if (BigDecimalUtils.isLe(rate, BigDecimalUtils.fifteen)) {
                log.info("tld checkBuyMatchData {}-{} 不符合买入条件,当前价格:{},涨停板价格:{},涨幅:{}", code, limitUpDate, realtimePrice,
                    data.getClosePrice(), rate);
                //stockLimitUpData.setTldFlag("d");
                //stockLimitUpData.setTldBuyInfo("涨幅过高，不适合买入");
                //stockLimitUpRepository.saveStockLimitUpData(stockLimitUpData);
                //缓存黑名单10分钟
                cacheService.put(cacheKey, "riseUp", 600);
                return null;
            }
        }

        stockDayDataList = StockUtils.getStockDayDataListBeforeDate(stockDayDataList, limitUpDate, 810);
        stockDayDataList.add(data);
        stockDayDataList.addAll(afterDayDataList);
        return matchTldBuyInfo(code, limitUpDate, stockDayDataList, BooleanUtils.TRUE);
    }

    /**
     * 校验是否匹配屠龙刀买入信号
     *
     * @param code
     * @param limitUpDate
     * @param stockDayDataList
     * @param b
     * @return
     */
    private TldMatchInfo matchTldBuyInfo(String code, String limitUpDate, List<StockDayData> stockDayDataList,
        String ignoreLowScoreFlag) {
        StockDayData data = StockUtils.getStockDayData(stockDayDataList, limitUpDate);
        List<StockDayData> afterDayDataList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, limitUpDate, 3);
        AddStrategyTradeStockDataRequest request = new AddStrategyTradeStockDataRequest();
        request.setEarlyWarnFlag(BooleanUtils.TRUE);
        request.setRealtimeFlag(BooleanUtils.TRUE);
        request.setIgnoreLowScoreFlag(ignoreLowScoreFlag);
        List<StrategyTradeDTO> strategyTradeDTOList = stockBackValidateService.queryBackValidateDateList(code,
            TulongdaoStockBackValidateService.STATEGY_CODE, stockDayDataList, request);
        if (CollectionUtils.isEmpty(strategyTradeDTOList)) {
            StockDayData stockDayData = afterDayDataList.get(afterDayDataList.size() - 1);
            log.info("tld checkMatchData {}-{}-{} 无匹配信息,涨停价格:{},当前价格:{},最低价格:{}", code,
                StockUtils.getStock(code).getName(), limitUpDate,
                data.getClosePrice(), stockDayData.getClosePrice(), stockDayData.getMinPrice());
            return null;
        }
        //过滤出信号日后的第一次买入数据
        Long time = DateUtil.parseDate(limitUpDate).getTime();
        strategyTradeDTOList = strategyTradeDTOList.stream().filter(t -> t.getBuyDate().getTime() > time).collect(
            Collectors.toList());
        if (CollectionUtils.isEmpty(strategyTradeDTOList)) {
            log.info("tld checkMatchData {}-{}-{} 无信号日后的买入匹配信息,当前价格:{}", code, StockUtils.getStock(code).getName(),
                limitUpDate, afterDayDataList.get(afterDayDataList.size() - 1).getClosePrice());
            return null;
        }
        StrategyTradeDTO strategyTradeDTO = strategyTradeDTOList.get(0);
        StockDayData buyData = strategyTradeDTO.getBuyData();
        TldMatchInfo tldMatchInfo = buyData.getTldMatchInfo();
        tldMatchInfo.setData(null);
        tldMatchInfo.setStockImageProfitType(strategyTradeDTO.getStockImageProfitType());
        tldMatchInfo.setStockImageCode(strategyTradeDTO.getStockImageCode());
        tldMatchInfo.setScore(strategyTradeDTO.getScore());

        String extraInfo = BooleanUtils.isTrue(tldMatchInfo.getEarlyWarnFlag()) ? "预警" : "";
        log.info("tld checkMatchData  {}-{}-{}-{}-匹配买入命中:{}", code, StockUtils.getStock(code).getName(), limitUpDate,
            extraInfo, JSON.toJSONString(tldMatchInfo));

        return tldMatchInfo;
    }

    private void tldSellLimitUpDataList(List<StockLimitUpData> limitUpDataList, Date date, boolean sendDingtalkFlag) {
        Map<String, StockLimitUpData> map = new HashMap<>();
        List<StockLimitUpData> invalidList = new ArrayList<>();
        for (StockLimitUpData data : limitUpDataList) {
            if (map.containsKey(data.getCode())) {
                invalidList.add(map.get(data.getCode()));
            }
            map.put(data.getCode(), data);
        }
        for (StockLimitUpData data : invalidList) {
            data.setTldSellFlag("d");
            stockLimitUpRepository.saveStockLimitUpData(data);
        }
        String dateStr = DateUtil.formatDate(date);
        List<String> codeList = limitUpDataList.stream().map(StockLimitUpData::getCode).collect(Collectors.toList());
        codeList = StockBlackListUtils.getCanBuyCodeList(codeList);
        List<StockLimitUpData> sellInfoList = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(codeList.size());
        for (String code : codeList) {
            executor.execute(() -> {
                log.info("{}-{}-tld sell start.", code, dateStr);

                StockLimitUpData limitUpData = map.get(code);
                TldMatchInfo tldMatchInfo = Objects.nonNull(limitUpData) ? limitUpData.getTldMatchInfo() : null;
                TldSellInfo sellInfo = Objects.nonNull(tldMatchInfo) ? tldMatchInfo.getSellInfo() : null;
                //获取屠龙刀卖出信息
                sellInfo = getTldSellInfo(code, sellInfo, limitUpData, date);
                log.info("{}-{}-tld sellInfo:{}", code, dateStr, JSON.toJSONString(sellInfo));
                if (Objects.nonNull(sellInfo) && !sellInfo.getSellType().equals(SELL_TYPE_LIMIT_UP_HOLD)) {
                    tldMatchInfo.setSellInfo(sellInfo);
                    limitUpData.setTldSellFlag(BooleanUtils.TRUE);
                    limitUpData.setTldSellPrice(sellInfo.getSellPrice());
                    limitUpData.setTldSellTime(sellInfo.getSellTime());
                    limitUpData.setTldProfitRate(BigDecimalUtils.subStractAndDividePrecent(sellInfo.getSellPrice(),
                        limitUpData.getTldBuyPrice()));
                    limitUpData.setTldSellInfo(sellInfo.getSellType());
                    limitUpData.setTldSellRemark(sellInfo.getSellRemark());
                    stockLimitUpRepository.saveStockLimitUpData(limitUpData);
                    sellInfoList.add(limitUpData);

                }
                log.info("{}-{}-tld sell finished.", code, dateStr);
                latch.countDown();
            });
        }
        try {
            latch.await(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (CollectionUtils.isNotEmpty(sellInfoList) && sendDingtalkFlag) {
            sendSellDingtalkMessage(date, sellInfoList, false);
        }
    }

    private void sendSellDingtalkMessage(Date date, List<StockLimitUpData> sellInfoList, Boolean isManual) {
        log.info("sendSellDingtalkMessage,stockCount:{}", sellInfoList.size());
        String manualInfo = isManual ? "手动" : "";
        String message = DateUtil.formatDate(date) + "股票通知-" + manualInfo + "卖出信号:<br/>" + buildSellMessage(
            sellInfoList);
        DingTalkMessage.Markdown markdown = new DingTalkMessage.Markdown();
        markdown.setText(message);
        markdown.setTitle("屠龙刀卖出信号通知");
        DingTalkMessage.At at = new DingTalkMessage.At();
        at.setIsAtAll(false);
        at.setAtMobiles(Lists.newArrayList("18057138912"));
        dingTalkService.sendMessage(DING_TALK_URL, new DingTalkMessage(markdown, at));
    }

    private String buildSellMessage(List<StockLimitUpData> sellInfoList) {
        List<String> messages = new ArrayList<>();
        for (StockLimitUpData data : sellInfoList) {
            String code = data.getCode();
            String message = "**" + DateUtil.format(data.getTldSellTime(), "HH:mm") + "-"
                + StockUtils.getSimpleCode(code) + "-" + StockUtils.getStock(code).getName() + "**,";
            if (!SELL_TYPE_END_SELL.equals(data.getTldSellInfo())) {
                String remark = StringUtils.isNotBlank(data.getTldSellRemark()) ? data.getTldSellRemark() + "," : "";
                message += remark + "买:" + data.getTldBuyPrice() + ",卖:" + data.getTldSellPrice()
                    + ",盈利:" + data.getTldProfitRate();
            } else {
                message += "买:" + data.getTldBuyPrice() + ",卖:" + data.getTldSellPrice() + ",盈利:"
                    + data.getTldProfitRate();
            }
            messages.add(message);
        }
        return StringUtils.join(messages, "<br/>");
    }

    private TldSellInfo getTldSellInfo(String code, TldSellInfo sellInfo, StockLimitUpData limitUpData, Date date) {
        if (Objects.isNull(sellInfo)) {
            sellInfo = new TldSellInfo();
        }
        Date beforeDate = WorkDayUtils.getBeforeWorkDay(date, 1);
        List<StockDayData> stockDayDataList = stockDayDataRepository.queryAll(code);
        StockDayData data = StockUtils.getStockDayData(stockDayDataList, DateUtil.formatDate(date));
        if (Objects.isNull(data)) {
            log.error("tld-getTldSellInfo-{}-{}获取数据失败", code, DateUtil.formatDate(date));
            return null;
        }
        StockDayData beforeData = StockUtils.getStockDayData(stockDayDataList, DateUtil.formatDate(beforeDate));
        if (Objects.isNull(data)) {
            log.error("tld-getTldSellInfo-{}-{}获取前一日数据失败", code, DateUtil.formatDate(beforeDate));
            return null;
        }
        BigDecimal beforeClosePrice = beforeData.getClosePrice();
        BigDecimal stopLossPrice = BigDecimalUtils.multiply(limitUpData.getTldBuyPrice(),
            BigDecimalUtils.zeroPointNineSeven);
        boolean isToday = DateUtil.formatDate(date).equals(DateUtil.formatDate(new Date()));
        List<StockDayData> fenshiList = null;
        if (isToday) {
            fenshiList = StockFenshiUtils.getFenshiList(code);
        } else {
            fenshiList = data.getFenshiList();
            if (CollectionUtils.isEmpty(fenshiList)) {
                fenshiList = new ArrayList<>();
            }
        }
        if (CollectionUtils.isNotEmpty(fenshiList)) {
            MacdIndicator.calculateMACD(fenshiList, 4);
            VolumeRatioIndicator.calculateVolumeRatio(fenshiList, 4);
            FenshiAverage.calculateFenshiAverage(fenshiList);
        }
        StockDayData sellData = StockFenshiUtils.getRiseUpData(fenshiList, beforeClosePrice);
        //爆量拉升未封板
        if (Objects.nonNull(sellData)) {
            sellInfo.setSellType(StockSellTypeEnums.getRiseUpData.getCode());
            sellInfo.setSellRemark(StockSellTypeEnums.getRiseUpData.getMessage() + ",谨慎观察卖出时机");
            sellInfo.setSellPrice(sellData.getClosePrice());
            sellInfo.setSellTime(DateUtil.parseDateTime(sellData.getDatetime()));
            return sellInfo;
        }
        sellData = StockFenshiUtils.getDivergeData(fenshiList, beforeClosePrice);
        if (Objects.nonNull(sellData)) {
            sellInfo.setSellType(StockSellTypeEnums.getDivergeData.getCode());
            sellInfo.setSellRemark(StockSellTypeEnums.getDivergeData.getMessage() + ",谨慎观察卖出时机");
            sellInfo.setSellPrice(sellData.getClosePrice());
            sellInfo.setSellTime(DateUtil.parseDateTime(sellData.getDatetime()));
            return sellInfo;
        }
        //炸板
        sellData = getExplodedData(fenshiList, beforeClosePrice);
        if (Objects.nonNull(sellData)) {
            sellInfo.setSellType(StockSellTypeEnums.getExplodedData.getCode());
            sellInfo.setSellRemark("涨停板炸板，赶紧卖出");
            sellInfo.setSellPrice(sellData.getClosePrice());
            sellInfo.setSellTime(DateUtil.parseDateTime(sellData.getDatetime()));
            return sellInfo;
        }
        //均线压力止损
        sellData = StockFenshiUtils.getAveragePressData(fenshiList, beforeClosePrice);
        if (Objects.nonNull(sellData)) {
            sellInfo.setSellType(StockSellTypeEnums.getAveragePressData.getCode());
            sellInfo.setSellRemark(StockSellTypeEnums.getAveragePressData.getMessage() + ",卖出止损");
            sellInfo.setSellPrice(sellData.getClosePrice());
            sellInfo.setSellTime(DateUtil.parseDateTime(sellData.getDatetime()));
            return sellInfo;
        }
        //暴跌后反弹遇阻
        sellData = StockFenshiUtils.getAverageReverseLtOpenPriceData(fenshiList, beforeClosePrice);
        if (Objects.nonNull(sellData)) {
            sellInfo.setSellType(StockSellTypeEnums.getAverageReverseLtOpenPriceData.getCode());
            sellInfo.setSellRemark(StockSellTypeEnums.getAverageReverseLtOpenPriceData.getMessage() + ",卖出止损");
            sellInfo.setSellPrice(sellData.getClosePrice());
            sellInfo.setSellTime(DateUtil.parseDateTime(sellData.getDatetime()));
            return sellInfo;
        }
        //到止损位止损
        sellData = getStopLossData(fenshiList, data, stopLossPrice);
        if (Objects.nonNull(sellData)) {
            sellInfo.setSellType(StockSellTypeEnums.getStopLossData.getCode());
            sellInfo.setSellRemark(StockSellTypeEnums.getStopLossData.getMessage() + ",卖出止损");
            sellInfo.setSellPrice(sellData.getClosePrice());
            sellInfo.setSellTime(DateUtil.parseDateTime(sellData.getDatetime()));
            return sellInfo;
        }
        if (isMarketEnd(date)) {
            if (StockUtils.isLimitUp(data)) {
                //封住涨停板
                sellInfo.setSellType(SELL_TYPE_LIMIT_UP_HOLD);
                sellInfo.setSellRemark("涨停板拿住,明天再说");
                return sellInfo;
            }
            //尾盘卖出
            sellInfo.setSellType(SELL_TYPE_END_SELL);
            sellInfo.setSellRemark("尾盘卖出");
            sellInfo.setSellPrice(data.getClosePrice());
            sellInfo.setSellTime(date);
            return sellInfo;
        }
        return null;
    }

    private StockDayData getStopLossData(List<StockDayData> fenshiList, StockDayData data, BigDecimal stopLossPrice) {
        //实时根据分时卖掉
        if (CollectionUtils.isNotEmpty(fenshiList)) {
            return StockFenshiUtils.getStopLossData(fenshiList, stopLossPrice);
        } else if (BigDecimalUtils.isLt(data.getOpenPrice(), stopLossPrice)) {
            //开盘跌破止损价
            StockDayData openData = BeanConvertUtils.convert(data, StockDayData.class);
            openData.setClosePrice(openData.getOpenPrice());
            openData.setMaxPrice(openData.getOpenPrice());
            openData.setMinPrice(openData.getOpenPrice());
            openData.setDatetime(openData.getDate() + " 09:30:00");
            return openData;
        } else if (BigDecimalUtils.isLt(data.getClosePrice(), stopLossPrice)) {
            //盘中跌破止损价
            StockDayData lossData = BeanConvertUtils.convert(data, StockDayData.class);
            lossData.setClosePrice(stopLossPrice);
            lossData.setMaxPrice(lossData.getOpenPrice());
            lossData.setMinPrice(stopLossPrice);
            //设成12:00，标识盘中的跌破止损价
            lossData.setDatetime(lossData.getDate() + " 12:00:00");
            return lossData;
        }
        return null;
    }

    private StockDayData getAveragePressData(List<StockDayData> fenshiList, BigDecimal beforeClosePrice) {
        if (CollectionUtils.isEmpty(fenshiList)) {
            return null;
        }
        StockDayData data = StockFenshiUtils.getAveragePressData(fenshiList, beforeClosePrice);
        if (Objects.nonNull(data)) {
            return data;
        }
        return null;
    }

    /**
     * 获取炸板数据
     *
     * @param fenshiList
     * @param beforeClosePrice
     * @return
     */
    private StockDayData getExplodedData(List<StockDayData> fenshiList, BigDecimal beforeClosePrice) {
        if (CollectionUtils.isEmpty(fenshiList)) {
            return null;
        }
        boolean limitUpFlag = false;
        int explodedCount = 0;
        for (StockDayData data : fenshiList) {
            BigDecimal price = data.getClosePrice();
            BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(price, beforeClosePrice);
            if (BigDecimalUtils.isGe(rate, BigDecimalUtils.ninePointSix)) {
                limitUpFlag = true;
                explodedCount = 0;
            }
            //涨停板后炸板到6个点卖出,如果6个点以上20分钟没有回封，卖出
            if (limitUpFlag && BigDecimalUtils.isLe(rate, BigDecimalUtils.nine)) {
                if (explodedCount <= 20) {
                    explodedCount++;
                }
                if (BigDecimalUtils.isLe(rate, BigDecimalUtils.six)) {
                    return data;
                }
                if (explodedCount > 20) {
                    return data;
                }
            }
        }
        return null;
    }

    /**
     * 获取macd顶背离数据
     *
     * @param fenshiList
     * @param beforeClosePrice
     * @return
     */
    private StockDayData getDivergeData(List<StockDayData> fenshiList, BigDecimal beforeClosePrice) {
        if (CollectionUtils.isEmpty(fenshiList)) {
            return null;
        }
        StockDayData data = StockFenshiUtils.getDivergeData(fenshiList, beforeClosePrice);
        if (Objects.nonNull(data)) {
            //收益5个点以上且非涨停板，才算是爆拉
            BigDecimal profitRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(), beforeClosePrice);
            if (BigDecimalUtils.isGt(profitRate, BigDecimalUtils.five) && BigDecimalUtils.isLe(profitRate,
                BigDecimalUtils.ninePointSix)) {
                return data;
            }
        }
        return null;
    }

    private boolean isMarketEnd(Date date) {
        Date now = new Date();
        Date todayBegin = DateUtil.beginOfDay(now);
        if (DateUtils.before(date, todayBegin)) {
            return true;
        }
        Date end = DateUtil.parseDateTime(DateUtil.formatDate(now) + " 14:53:00");
        return DateUtils.after(now, end);
    }

    private StockDayData getRiseUpData(List<StockDayData> fenshiList, BigDecimal beforeClosePrice) {
        if (CollectionUtils.isEmpty(fenshiList)) {
            return null;
        }
        StockDayData data = StockFenshiUtils.getRiseUpData(fenshiList, beforeClosePrice);
        if (Objects.nonNull(data)) {
            //大于5个点,直接返回
            if (BigDecimalUtils.isGe(data.getFenshiRate(), BigDecimalUtils.five)) {
                return data;
            }
            //当天收益1个点以上且非涨停板，才算是爆拉
            BigDecimal profitRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(), beforeClosePrice);
            if (BigDecimalUtils.isGt(profitRate, BigDecimalUtils.one) && BigDecimalUtils.isLe(profitRate,
                BigDecimalUtils.ninePointSix)) {
                return data;
            }
        }
        return null;
    }

    private void sendWarnDingtalkMessage(String message, String code, String date, String type) {
        String cacheKey = "dingMsg:" + code + "_" + date + "_" + type;
        String value = cacheService.get(cacheKey);
        if (StringUtils.isNotBlank(value)) {
            return;
        }
        cacheService.put(cacheKey, type);
        DingTalkMessage.Markdown markdown = new DingTalkMessage.Markdown();
        markdown.setText("股票通知:" + message);
        markdown.setTitle("屠龙刀错误信息通知");
        DingTalkMessage.At at = new DingTalkMessage.At();
        at.setIsAtAll(false);
        at.setAtMobiles(Lists.newArrayList("18057138912"));
        dingTalkService.sendMessage(DING_TALK_URL, new DingTalkMessage(markdown, at));
    }

    private StockDayData buildTomarrowData(StockSupportDTO stockSupportDTO,
        List<StockDayData> stockDayDataList, BigDecimal limitUpPrice) {
        BigDecimal limitLowPrice = BigDecimalUtils.multiply(limitUpPrice, BigDecimalUtils.zeroPointNine);
        StockDayData beforeData = stockDayDataList.get(stockDayDataList.size() - 1);
        String date = DateUtil.formatDate(WorkDayUtils.getNextWorkDay(DateUtil.parseDate(beforeData.getDate())));
        StockDayData stockDayData = new StockDayData();
        stockDayData.setCode(stockSupportDTO.getCode());
        stockDayData.setDate(date);
        BigDecimal firstPrice = stockSupportDTO.getFirst().getBuyPrice();
        stockDayData.setOpenPrice(firstPrice);
        stockDayData.setClosePrice(firstPrice);
        stockDayData.setMaxPrice(BigDecimalUtils.multiply(firstPrice, BigDecimalUtils.onePointZeroOne));
        BigDecimal minPrice = firstPrice;
        if (Objects.nonNull(stockSupportDTO.getThird())) {
            minPrice = stockSupportDTO.getThird().getPrice();
        } else if (Objects.nonNull(stockSupportDTO.getSecond())) {
            minPrice = stockSupportDTO.getSecond().getPrice();
        }
        minPrice = BigDecimalUtils.multiply(minPrice, BigDecimalUtils.zeroPointNineEight);
        if (BigDecimalUtils.isLt(minPrice, limitLowPrice)) {
            minPrice = limitLowPrice;
        }
        stockDayData.setMinPrice(minPrice);
        stockDayData.setTradeNum(beforeData.getTradeNum());
        return stockDayData;
    }
}
